package wviz.gui.graph;

import prefuse.Visualization;
import prefuse.action.*;
import prefuse.action.animate.ColorAnimator;
import prefuse.action.animate.QualityControlAnimator;
import prefuse.action.animate.VisibilityAnimator;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.FontAction;
import prefuse.action.filter.FisheyeTreeFilter;
import prefuse.action.layout.CollapsedSubtreeLayout;
import prefuse.activity.SlowInSlowOutPacer;
import prefuse.controls.*;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.expression.OrPredicate;
import prefuse.data.search.PrefixSearchTupleSet;
import prefuse.data.search.SearchTupleSet;
import prefuse.data.tuple.CompositeTupleSet;
import prefuse.data.tuple.TupleSet;
import prefuse.render.AbstractShapeRenderer;
import prefuse.render.DefaultRendererFactory;
import prefuse.util.ColorLib;
import prefuse.util.GraphicsLib;
import prefuse.util.display.DisplayLib;
import prefuse.visual.VisualItem;
import prefuse.visual.expression.InGroupPredicate;
import prefuse.visual.expression.StartVisiblePredicate;
import prefuse.visual.expression.VisiblePredicate;
import prefuse.visual.sort.TreeDepthItemSorter;
import wviz.docburst.control.MouseWheelControl;
import wviz.docburst.layout.StarburstLayout;
import wviz.docburst.render.ArcLabelRenderer;
import wviz.docburst.render.DecoratorLabelRenderer;
import wviz.docburst.render.SectorRenderer;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;

import wviz.gui.color.NodeColorAction;
import wviz.gui.color.TextColorAction;

/**
 * Demonstration of a radial space-filling tree viewer.
 * <p/>
 * Generalized fisheye filter applied, revealing only nodes of depth 1 from focus (root), selected nodes (gold), and search results (orange).
 * Select nodes with a single click.  Select multiple with ctrl-click.  Deselect with ctrl-click on a selected node.
 * Recenter graph by double clicking any node; new spanning tree is calculated and the graph will be shown centered at that
 * node.
 * <p/>
 * There are some strange rendering effects, but these are documented Java2D bugs with Arc2D rendering (see notes in Sector2D class).
 *
 * @author Christopher Collins through modification of original code by jeffrey heer
 * @version 1.1
 *          <p/>
 *          maniplulated by : Hadi
 *          Date: 11-Jun-2008
 */
public class RadialDisplay extends BaseDisplay {
    private static final String tree = "tree";
    public static final String TREE_NODES = "tree.nodes";
    private static final String treeEdges = "tree.edges";
    private static final String labels = "labels";

    private String m_label;
    private Action resizeAction;
    private FisheyeTreeFilter fisheyeTreeFilter;

    ////////////////////////////
    ////// CONSTRUCTOR
    ////////////////////////////

    public RadialDisplay(Graph g) {
        this(g, "word", 10, "Filter");
    }

    public RadialDisplay(Graph g, String label, int filterMaxValue, String filterLabel) {
        super(new Visualization(), TREE_NODES, filterMaxValue, filterLabel, Visualization.SEARCH_ITEMS);
        m_label = label;
        setupGraph(g);
    }

    public void setAutoResize(boolean autoResize) {
        resizeAction.setEnabled(autoResize);
    }

    public void setFilterLevel(int level) {
        fisheyeTreeFilter.setDistance(level);
        m_vis.run("filter");
    }

    @Override
    public void setupGraph(Graph g) {
        /**
        //because we re-create the display every time, the following lines is useless
        if (m_vis.getGroup(tree) != null) {
            //group already added to this graph, remove it first
            m_vis.removeGroup(Visualization.SEARCH_ITEMS); //remove focus group
            m_vis.removeGroup(tree); //remove primary graph data nodes
        }
         */

        // -- set up visualization --
        m_vis.add(tree, g);
        m_vis.setVisible(treeEdges, null, false);

        // -- set up renderers --
        DefaultRendererFactory rf = createRenderers();
        m_vis.setRendererFactory(rf);

        // -- set up processing actions --

        // create the tree layout color; adds layout schema to nodes
        StarburstLayout treeLayout = new StarburstLayout(tree);
        // set location and turn off autoscale so graph layout doesn't revert to original view when mouse wheel is rolled
        treeLayout.setAutoScale(false);
        treeLayout.setLayoutAnchor(new Point2D.Double());
        // Uncomment next line to restrict graph to semi-circle
//		treeLayout.setAngularBounds(-Math.PI/2, Math.PI);  // TODO add widget to interactively adjust this
        m_vis.putAction("treeLayout", treeLayout);

        // add decorators (has to be after layout because decorators rendered rely on Schema provided by StarburstLayout
        m_vis.addDecorators(labels, TREE_NODES, new OrPredicate(new VisiblePredicate(), new StartVisiblePredicate()), ArcLabelRenderer.LABEL_SCHEMA);

        // fonts and colors for decorator items (labels)
        FontAction fonts = new StarburstLayout.ScaleFontAction(labels, m_label);
        ItemAction textColor = new TextColorAction(labels);

        CollapsedSubtreeLayout subLayout = new CollapsedSubtreeLayout(tree);
        m_vis.putAction("subLayout", subLayout);

        // define focus groups
        TupleSet selected = m_vis.getFocusGroup(Visualization.SELECTED_ITEMS);
        SearchTupleSet search = new PrefixSearchTupleSet();
        m_vis.addFocusGroup(Visualization.SEARCH_ITEMS, search);

        // filter the tree to 2 levels from selected items and search results
        CompositeTupleSet searchAndSelect = new CompositeTupleSet();
        searchAndSelect.addSet(Visualization.SELECTED_ITEMS, m_vis
                .getFocusGroup(Visualization.SELECTED_ITEMS));
        searchAndSelect.addSet(Visualization.SEARCH_ITEMS, m_vis
                .getFocusGroup(Visualization.SEARCH_ITEMS));
        m_vis.addFocusGroup("searchAndSelect", searchAndSelect);
        fisheyeTreeFilter = new FisheyeTreeFilter(tree, "searchAndSelect", 1);

        // colors
        ItemAction nodeColor = new NodeColorAction(TREE_NODES);
        ColorAction nodeStrokeColor = new ColorAction(TREE_NODES, VisualItem.STROKECOLOR) {
            public int getColor(VisualItem item) {
                return ColorLib.darker(item.getFillColor());
            }
        };

        // recolor
        ActionList recolor = new ActionList();
        recolor.add(nodeColor);
        recolor.add(nodeStrokeColor);
        recolor.add(textColor);
        m_vis.putAction("recolor", recolor);

        // animate paint change
        ActionList animatePaint = new ActionList(400);
        animatePaint.add(new ColorAnimator(TREE_NODES));
        animatePaint.add(new RepaintAction());
        m_vis.putAction("animatePaint", animatePaint);

        // recentre and rezoom on reload
        resizeAction = new Action() {
            public void run(double frac) {
                // animate reset zoom to fit the data (must run only AFTER layout)
                Rectangle2D bounds = m_vis.getBounds(tree);

                if (bounds.getWidth() == 0) return;
                GraphicsLib.expand(bounds, 10 + (int) (1 / m_vis.getDisplay(0).getScale()));
                DisplayLib.fitViewToBounds(m_vis.getDisplay(0), bounds, (long) 1250);
            }
        };
        m_vis.putAction("resize", resizeAction);

        // create the filtering and layout
        ActionList filter = new ActionList();
        filter.add(fisheyeTreeFilter);
        filter.add(new TreeRootAction(tree));
        filter.add(treeLayout);
        filter.add(new StarburstLayout.LabelLayout(labels));
        filter.add(fonts);
        filter.add(subLayout);
        filter.add(textColor);
        filter.add(nodeColor);
        filter.add(nodeStrokeColor);
        m_vis.putAction("filter", filter);

        // animated transition
        final ActionList animate = new ActionList(1250);
        animate.setPacingFunction(new SlowInSlowOutPacer());
        animate.add(new QualityControlAnimator());
        animate.add(new VisibilityAnimator(tree));
        animate.add(new ColorAnimator(TREE_NODES));
        animate.add(new VisibilityAnimator(labels));
        animate.add(new ColorAnimator(labels));
        animate.add(new RepaintAction());
        m_vis.putAction("animate", animate);
        m_vis.alwaysRunAfter("filter", "animate");
        m_vis.alwaysRunAfter("animate", "resize");

        // repaint
        ActionList repaint = new ActionList() {
            public void run(double frac) {
                // only repaint if animation is not already running; otherwise we get flicker if repaint
                // is called from HoverActionControl while a visibility animation is running
                if (!animate.isRunning()) {
                    super.run(frac);
                }
            }
        };
        repaint.add(recolor);
        repaint.add(new RepaintAction());
        m_vis.putAction("repaint", repaint);

        // initialize the display
        setSize(600, 600);
        setItemSorter(new TreeDepthItemSorter());
        addControlListener(new ZoomToFitControl());
        addControlListener(new ZoomControl());
        addControlListener(new PanControl(false));
        addControlListener(new FocusControl(2, "filter"));
        addControlListener(new FocusControl(Visualization.SELECTED_ITEMS, 1, "filter"));
        addControlListener(new HoverActionControl("repaint"));
        addControlListener(new MouseWheelControl("filter", "angleFactor"));

        // filter graph and perform layout
        m_vis.run("filter");

        // maintain a set of items that should be interpolated linearly
        // this isn't absolutely necessary, but makes the animations nicer
        // the PolarLocationAnimator should read this set and act accordingly

        selected.addTupleSetListener(new TupleSetListener() {
            public void tupleSetChanged(TupleSet t, Tuple[] add, Tuple[] rem) {
                m_vis.cancel("animate");
                m_vis.run("filter");
            }
        });

        search.addTupleSetListener(new TupleSetListener() {
            public void tupleSetChanged(TupleSet t, Tuple[] add, Tuple[] rem) {
                m_vis.cancel("animate");
                m_vis.run("filter");
            }
        });
    }

    /**
     * Switch the root of the tree by requesting a new spanning tree
     * at the desired root and hiding all nodes above
     */
    public static class TreeRootAction extends GroupAction {
        public TreeRootAction(String graphGroup) {
            super(graphGroup);
        }

        public void run(double frac) {
            TupleSet focus = m_vis.getGroup(Visualization.FOCUS_ITEMS);
            if (focus == null || focus.getTupleCount() == 0)
                return;

            Graph g = (Graph) m_vis.getGroup(m_group);
            Node f = null;
            Iterator tuples = focus.tuples();
            while (tuples.hasNext()
                    && !g.containsTuple(f = (Node) tuples.next())) {
                f = null;
            }
            if (f == null)
                return;
            g.getSpanningTree(f);
        }
    }

    /**
     * Set node text colors
     * @return renderer factory for rendering this graph visual items
     */
    private DefaultRendererFactory createRenderers() {
        DefaultRendererFactory rf = new DefaultRendererFactory();

        //renderer to draw shapes for filled nodes
        SectorRenderer sectorRenderer = new SectorRenderer();
        sectorRenderer.setRenderType(AbstractShapeRenderer.RENDER_TYPE_DRAW_AND_FILL);

        // for angular rotating of non-curved labels
        DecoratorLabelRenderer decoratorLabelRenderer = new DecoratorLabelRenderer(m_label, false, 2);
        // decoratorLabelRenderer.setHorizontalAlignment(Constants.LEFT);

        // for arching of labels within node
        ArcLabelRenderer arcLabelRenderer = new ArcLabelRenderer(m_label, 2, 30);

        // set up RendererFactory
        rf.add("ingroup('labels') and rotation == 0", arcLabelRenderer); // all sector labels that are not rotated
        rf.add("ingroup('labels') and rotation != 0", decoratorLabelRenderer); // all rotated sector labels
        rf.add(new InGroupPredicate(treeEdges), null);
        rf.setDefaultRenderer(sectorRenderer); // filled sectors
        return rf;
    }
}